#include <iostream>
#include <chrono>
#include <boost/beast/core.hpp> 
#include <boost/beast/http.hpp>
#include <boost/asio.hpp>
#include "AiClient.h"

AiClient::AiClient(::boost::asio::io_context& ioc, const char* host, const char* port) : 
	_ioc(ioc), _host(host), _port(port),
	_resolver(::boost::asio::make_strand(_ioc)),
	_stream(::boost::asio::make_strand(_ioc))
{
}

AiClient::~AiClient()
{
	_resolver.cancel();
	if (_stream.socket().is_open())
	{
		::boost::beast::error_code ec;
		_stream.socket().shutdown(::boost::asio::ip::tcp::socket::shutdown_both, ec);
		_stream.close();
	}
}

void AiClient::post(const std::string& target, const std::string& body)
{
	if (!_addDataCallback)
	{
		std::cout << "没有初始化addDataCallback" << std::endl;
		return;
	}

	if (!_sendDataCallback)
	{
		std::cout << "没有初始化sendDataCallback" << std::endl;
		return;
	}

	if (!_finishDataCallback)
	{
		std::cout << "没有初始化finishDataCallback" << std::endl;
		return;
	}

	_req.version(11);
	_req.method(::boost::beast::http::verb::post);
	_req.target(target);
	_req.set(::boost::beast::http::field::host, _host);
	_req.set(::boost::beast::http::field::content_type, "application/json");
	_req.body() = body;
	_req.prepare_payload();

	auto self = shared_from_this();
        _resolver.async_resolve(
		_host,
		_port,
		::boost::beast::bind_front_handler(
			&AiClient::onResolve, self));	
}

void AiClient::setAddDataCallback(AddDataCallback callback) 
{
	_addDataCallback = std::move(callback);
}

void AiClient::setSendDataCallback(SendDataCallback callback) 
{
	_sendDataCallback = std::move(callback);
}

void AiClient::setFinishDataCallback(FinishDataCallback callback) 
{
	_finishDataCallback = std::move(callback);
}

void AiClient::onResolve(::boost::beast::error_code ec, ::boost::asio::ip::tcp::resolver::results_type results)
{
	if (ec) 
		return fail(ec, "resolve");

        _stream.expires_after(std::chrono::seconds(30));
        _stream.async_connect(
			results,
			::boost::beast::bind_front_handler(
				&AiClient::onConnect,
				shared_from_this()));
}

void AiClient::onConnect(::boost::beast::error_code ec, ::boost::asio::ip::tcp::resolver::results_type::endpoint_type)
{
        if (ec) 
		return fail(ec, "connect");

        _stream.expires_after(std::chrono::seconds(30));
        ::boost::beast::http::async_write(
		_stream,
		_req,
		::boost::beast::bind_front_handler(
			&AiClient::onWrite,
			shared_from_this()));
}

void AiClient::onWrite(::boost::beast::error_code ec, size_t)
{
	if (ec) 
		return fail(ec, "write");

	::boost::beast::http::async_read_header(
		_stream,
		_buffer,
		_parser,
		::boost::beast::bind_front_handler(
			&AiClient::onReadHeader,
			shared_from_this()));
}

void AiClient::onReadHeader(::boost::beast::error_code ec, size_t)
{
	if (ec) 
		return fail(ec, "readHeader");

#if 0
	const auto& req = _parser.get();
	std::cout << "AI服务器响应: " << _host << ":" << _port << '\n';
	std::cout << "\thead:" << '\n';                                                              
	for (auto& field : req)                                                                      
	        std::cout << "\t\t" << field.name_string() << ": " << field.value() << '\n';         
#endif

	::boost::beast::http::async_read_some(
		_stream,
		_buffer,
		_parser,
		::boost::beast::bind_front_handler(
			&AiClient::onReadBody,
			shared_from_this()));
}

void AiClient::onReadBody(::boost::beast::error_code ec, size_t)
{
	if (ec && ec != ::boost::beast::http::error::end_of_chunk)
		return fail(ec, "readBody");

	std::string_view responseSV = "response";
	size_t responseSVN = responseSV.size();
	std::string_view body = _parser.get().body();
	auto bodyI = body.find_last_of('\n');
	if (bodyI != std::string_view::npos && bodyI > _bodyProcessed)
	{
		std::string_view content = body.substr(_bodyProcessed, bodyI - _bodyProcessed);
		size_t begI = 0;
		size_t findI = 0;
		do
		{
			 _bodyProcessed = bodyI;
			findI = content.find_first_of('\n', begI +1);
			size_t endI = findI == std::string_view::npos ? content.size() : findI;
			std::string_view line = content.substr(begI, endI - begI);
			begI = endI;
			size_t i = 0;
			size_t lineN = line.size();
			for (; i + responseSVN < lineN; ++i)
			{
				if (responseSV.compare(line.substr(i, responseSVN)))
					continue;
				else
					break;
			}

			size_t valBeg = lineN, valEnd = lineN;
			for (size_t j = i + responseSVN + 1; j < lineN; ++j)
			{
				if (line[j] != '\"')
					continue;

				if (line[j - 1] == '\\')
					continue;

				if (valBeg < lineN)
				{			
					valEnd = j;
					break;
				}
				else
				{
					valBeg = j + 1;
				}
			}

			if (valBeg < valEnd)
			{
				_addDataCallback(line.substr(valBeg, valEnd - valBeg));
			}
		} while(findI != std::string_view::npos);
				
		_sendDataCallback();
	}

	if (_parser.is_done()) 
	{
		_stream.socket().shutdown(::boost::asio::ip::tcp::socket::shutdown_both, ec);
		_finishDataCallback(); 
		_sendDataCallback();
	}
	else
	{
		_stream.expires_after(_timeout);
	        ::boost::beast::http::async_read_some(
			_stream,
			_buffer,
			_parser,
			::boost::beast::bind_front_handler(
			        &AiClient::onReadBody,
			        shared_from_this()));
	}
}

void AiClient::fail(::boost::beast::error_code ec, const char* what)
{
	std::cout << what << ": " << ec.message() << std::endl;
}
